# 本文件用于计算互换期权的风险特征并做图
# 作者：季俊男
# 创建日期：20190610
# 修改日期：20190714

import numpy as np
import pandas as pd
from math import log, exp, sqrt, pi, exp
from scipy.stats import norm
import functools
from pylab import mpl
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FixedLocator, FixedFormatter
from matplotlib.dates import DateLocator



def call(T, F, X, sigma, A):
    """使用正态分布的Black模型进行估值的支付方互换期权"""
    d = (F - X) / (sigma * sqrt(T))
    price = A * sigma * sqrt(T) * (1/sqrt(2*pi)*exp(-d**2/2) + d*norm.cdf(d))
    return price


def put(T, F, X, sigma, A):
    """使用正态分布的Black模型进行估值的收取方互换期权"""
    d = (F - X) / (sigma * sqrt(T))
    price = A * sigma * sqrt(T) * (1/sqrt(2*pi)*exp(-d**2/2) - d*norm.cdf(-d))
    return price


def get_vol(data, N):
    """计算收盘价-收盘价绝对波动率"""
    res = []
    num = len(data)
    for i in range(num+1-N):
        d = data[i : i+N]
        dd = [(d[k + 1] - d[k]) for k in range(N - 1)]
        res.append(np.std(dd, ddof=1))
    return res


class Spread(object):
    """构建一个价差策略，由不同的期权合约组成，用来计算价差对某个参数的敏感性"""

    def __init__(self, types: list, positions: list, par=10000000):
        """type参数表示够成价差的期权的类型，positions表示仓位，正值表示做多，负值表示做空
        par表示一份合约的面值"""
        self.types = types
        self.positions = positions
        self.par = 10000000
        self.fun = {"call": call, "put": put}

    def create_cal_value(self, factor_name, params):
        """本方法用于创建计算价差价值的函数，唯一的输入参数是敏感性相关的变量的值"""

        def cal_value(factor):
            res = 0
            for t, p, param in zip(self.types, self.positions, params):
                res = res + self.par * p * functools.partial(self.fun[t], **param)(**{factor_name: factor})
            return res

        return cal_value

    def get_values(self, factor_name, factor_range, others: list):
        """计算价差价值对某个因素的敏感性，实现方法是划定某个因素的范围与单位距离，
        将该因素的每个假设值代入cal_value计算价差的理论价值，
        参数factor是指选定的参数，facor_range是列表形式的选定参数的取值范围，
        others则是字典形式其他因素的固定值"""
        res = []
        cal_value = self.create_cal_value(factor_name, others)
        res = map(cal_value, factor_range)
        return list(res)


class YangZhangVol(object):
    """本类用于计算数据序列的Yang-Zhang波动率序列"""
    def __init__(self, data):
        """接受二维的样本数据data，一行为一个交易周期，列则分别为交易周期内的开盘价、收盘价、最高价与最低价"""
        self.data = data
        self.o, self.c, self.h, self.l = list(zip(*data))
        self.num = len(data)

    def sigma0_square(self, N):
        """N则表示以滚动N个交易周期估算波动率，最后形成一个波动率的时间序列"""
        res = []
        for i in range(self.num+1-N):
            d = self.o[i:i+N]
            dd = [(d[k+1]-d[k]) for k in range(N-1)]
            res.append(np.var(dd, ddof=1))
        return res

    def sigma1_square(self, N):
        res = []
        for i in range(self.num + 1 - N):
            d = self.c[i:i + N]
            dd = [(d[k + 1] - d[k]) for k in range(N - 1)]
            res.append(np.var(d, ddof=1))
        return res

    def sigma2_square(self, N):
        res = []
        for i in range(self.num + 1 - N):
            d = self.data[i:i+N]
            dd = [((d[k][2]-d[k][1])*(d[k][2]-d[k][0]) + (d[k][3]-d[k][1])*(d[k][3]-d[k][0])) for k in range(N)]
            res.append(np.mean(d))
        return res

    def get_vol(self, N, year=252):
        """year表示周期波动率年化，默认为将日波动率年化"""
        sigma0s = self.sigma0_square(N)
        sigma1s = self.sigma1_square(N)
        sigma2s = self.sigma2_square(N)
        k = 0.34/(1.34+(N+1)/(N-1))
        res = []
        for a, b, c in zip(sigma0s, sigma1s, sigma2s):
            res.append(np.sqrt(year*(a + k*b + (1-k)*c)))
        return res


class GammaPlot(object):
    """绘制价差理论价值的Gamma风险的敏感度图"""

    def __init__(self, factor_range, cost_factor):
        self.factor_range = factor_range
        self.k = [1, 1.13291268511233, 48.79884977140827, 8.523736347322163]
        self.cost_factor = [cost_factor]

    def spread1(self):
        """跨式期权空头"""
        others = [{"X": 0.03, "A": 0.9879335473976283, "T": 1, "sigma": 0.01},
                  {"X": 0.03, "A": 0.9879335473976283, "T": 1, "sigma": 0.01}]
        factor = "F"
        s1 = Spread(["call", "put"], [-1 * self.k[0], -1 * self.k[0]])
        cost = s1.get_values(factor, self.cost_factor, others)[0]
        res = [(r - cost) for r in s1.get_values(factor, self.factor_range, others)]
        return res

    def spread2(self):
        """宽跨式期权空头"""
        others = [{"X": 0.035, "A": 0.9879335473976283, "T": 1, "sigma": 0.01},
                  {"X": 0.025, "A": 0.9879335473976283, "T": 1, "sigma": 0.01}]
        factor = "F"
        s2 = Spread(["call", "put"], [-1 * self.k[1], -1 * self.k[1]])
        cost = s2.get_values(factor, self.cost_factor, others)[0]
        res = [(r - cost) for r in s2.get_values(factor, self.factor_range, others)]
        return res

    def spread3(self):
        """看涨期权比例垂直价差"""
        others = [{"X": 0.025, "A": 0.9879335473976283, "T": 1, "sigma": 0.01},
                  {"X": 0.035, "A": 0.9879335473976283, "T": 1, "sigma": 0.01}]
        factor = "F"
        s3 = Spread(["call", "call"], [1, -2.31])
        cost = s3.get_values(factor, self.cost_factor, others)[0]
        res = [(r - cost) for r in s3.get_values(factor, self.factor_range, others)]
        return res

    def spread4(self):
        """蝶式期权多头"""
        others = [{"X": 0.025, "A": 0.9879335473976283, "T": 1, "sigma": 0.01},
                  {"X": 0.03, "A": 0.9879335473976283, "T": 1, "sigma": 0.01},
                  {"X": 0.035, "A": 0.9879335473976283, "T": 1, "sigma": 0.01}]
        factor = "F"
        s4 = Spread(["call", "call", "call"], [1 * self.k[3], -2 * self.k[3], 1 * self.k[3]])
        cost = s4.get_values(factor, self.cost_factor, others)[0]
        res = [(r - cost) for r in s4.get_values(factor, self.factor_range, others)]
        return res

    def plot(self):
        """画图"""
        s1 = self.spread1()
        s2 = self.spread2()
        s3 = self.spread3()
        s4 = self.spread4()
        fig, ax = plt.subplots(1, 1, figsize=(8, 4))
        ax.plot(self.factor_range, s1, label="跨式期权空头")
        ax.plot(self.factor_range, s2, label="宽跨式期权空头")
        ax.plot(self.factor_range, s3, label="支付方互换期权比例垂直价差")
        ax.plot(self.factor_range, s4, label="支付方互换期权蝶式价差多头")
        ax.set_xlabel("远期利率")
        ax.set_ylabel("价差理论边际")
        ax.legend()
        fig.show()


class CC_Sigma_Plot(object):
    """绘制收盘价-收盘价波动率图，包含两个维度，一个是5天（一星期），一个是20天（一个月）"""

    def __init__(self, data1, data2, dts, n1=5, n2=20):
        """根据读取csv文件, 其中data1是1YFR007互换利率，data2是5YFR007互换利率数据，dts则是日期序列"""
        self.data1 = data1
        self.data2 = data2
        self.dts = dts
        self.n1 = n1
        self.n2 = n2

    @staticmethod
    def get_vol(data, N):
        """计算收盘价-收盘价绝对波动率"""
        res = []
        num = len(data)
        for i in range(num - N):
            d = data[i: i + N + 1]
            dd = [(d[k + 1] - d[k]) for k in range(N)]
            res.append(np.std(dd, ddof=1))
        res = 100 * np.array(res)
        return res

    def plot(self):
        fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex="all")
        minor_locator = FixedLocator([29, 71])  # 划分时间段，分割点分别为2.15日和4.17日
        # 互换利率曲线作图
        d1 = self.data1[self.n2:]
        d2 = self.data2[self.n2:]
        axes[0].plot(self.dts, d1, linestyle="-", label="1Y_FR007互换")
        axes[0].plot(self.dts, d2, linestyle="--", label="5Y_FR007互换")
        axes[0].legend()
        axes[0].set_title("互换利率走势图(%)")
        axes[0].xaxis.set_minor_locator(minor_locator)
        axes[0].grid(axis="x", which="minor", linestyle="--")
        # 1Y_FR007互换波动率作图
        d3 = self.get_vol(self.data1[self.n2 - self.n1:].values, self.n1)
        d4 = self.get_vol(self.data1.values, self.n2)
        axes[1].plot(self.dts, d3, linestyle="--", label="5D波动率")
        axes[1].plot(self.dts, d4, linestyle="-", label="20D波动率")
        axes[1].legend()
        axes[1].set_title("1Y_FR007互换5D与20D波动率(bp)")
        axes[1].xaxis.set_minor_locator(minor_locator)
        axes[1].grid(axis="x", which="minor")
        # 5Y_FR007互换波动率作图
        d5 = self.get_vol(self.data2[self.n2 - self.n1:].values, self.n1)
        d6 = self.get_vol(self.data2.values, self.n2)
        axes[2].plot(self.dts, d5, linestyle="--", label="5D波动率")
        axes[2].plot(self.dts, d6, linestyle="-", label="20D波动率")
        axes[2].legend()
        axes[2].set_title("5Y_FR007互换5D与20D波动率(bp)")
        axes[2].xaxis.set_minor_locator(minor_locator)
        axes[2].grid(axis="x", which="minor")
        # 展示
        fig.savefig("lll.png")


def plot1():
    df1 = pd.read_csv("IRS_FR007_5Y.csv", header=0, index_col=0, parse_dates=True, engine="python")
    df2 = 100*df1.diff()
    fig, ax = plt.subplots(1, 1, figsize=(8, 4), sharey="all")
    ax.scatter(df1, df2, s=10)
    ax.set_xlabel("IRS_FR007_5Y 固定端收益率（%）")
    ax.set_ylabel("日涨跌（bp）")
    fig.show()


def plot2():
    factor_range = list(np.arange(0.02, 0.04, 0.0001))
    gamma_plot = GammaPlot(factor_range, 0.029)
    gamma_plot.plot()


def plot3():
    data1 = pd.read_csv("IRS_FR007_1Y.csv", header=0, index_col=0, engine="python", parse_dates=True)["2018-12-4":]
    data2 = pd.read_csv("IRS_FR007_5Y.csv", header=0, index_col=0, engine="python", parse_dates=True)["2018-12-4":]
    dts = data1.index[20:]
    sigma_plot = CC_Sigma_Plot(data1, data2, dts)
    sigma_plot.plot()


mpl.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plot3()